package com.chat.login.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chat.common.common.constant.MailConstant;
import com.chat.common.common.constant.MessageConstant;
import com.chat.common.common.constant.RedisKeyConstant;
import com.chat.common.common.exception.RoutineException;
import com.chat.common.entity.*;
import com.chat.common.vo.UserVo;
import com.chat.login.config.JwtProperties;
import com.chat.login.domain.dto.*;
import com.chat.login.domain.vo.LoginUserVO;
import com.chat.login.domain.vo.VersionNumberVO;
import com.chat.login.mapper.UserMapper;
import com.chat.login.service.*;
import com.chat.login.utils.EmailUtils;
import com.chat.common.json.JacksonObjectMapper;
import com.chat.login.utils.JwtTool;
import com.chat.login.utils.RedisUtil;
import com.chat.common.vo.R;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author weikjie
 * @since 2024-03-08
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private PasswordEncoder passwordEncoder;
    //jwt工具
    @Autowired
    private JwtTool jwtTool;
    //jwt配置
    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private EmailUtils emailUtils;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private IUserAppealService iUserAppealService;

    @Autowired
    private IDisableAccountService idisableAccountService;

    @Autowired
    private IUserFeedbackService iUserFeedbackService;

    @Autowired
    private IVersionService iVersionService;

    @Override
    public R<LoginUserVO> login(LoginUserDTO loginUserDto) {
        User user = baseMapper
                .selectOne(
                        new LambdaQueryWrapper<User>()
                                .eq(User::getAccount, loginUserDto.getAccount())
                );
        if (user==null){
            //判断账号是否存在
            throw new RoutineException(MessageConstant.ACCOUNT_NOT_FOUND);
        } else if(!user.getState().equals(1)){
            //判断账号是否被锁定
            throw new RoutineException(MessageConstant.ACCOUNT_LOCKED);
        } else if(!passwordEncoder.matches(loginUserDto.getPassword(), user.getPassword())){
            //判断密码是否错误
            throw new RoutineException(MessageConstant.PASSWORD_ERROR);
        } else {
            //判断当前用户是否已经登录
            String oldToken = (String) redisUtil.get(RedisKeyConstant.USER_ACCOUNT_ForToken + user.getAccount());
            if(oldToken != null && !oldToken.isEmpty()){
                //清除之前的登录凭证
                redisUtil.delete(RedisKeyConstant.TOKEN_USER + oldToken);
                redisUtil.delete(RedisKeyConstant.TOKEN_ACCOUNT + oldToken);
                redisUtil.delete(RedisKeyConstant.USER_ACCOUNT_ForToken + user.getAccount());
            }
            //创建token
            String token = jwtTool.createToken(user.getId(),jwtProperties.getTokenTTL());
            //把token存入redis
            redisUtil.setEx(RedisKeyConstant.TOKEN_USER + token, user.getId(), 12, TimeUnit.HOURS);
            //存用户登录信息
            redisUtil.setEx(RedisKeyConstant.USER_ACCOUNT_ForToken + user.getAccount(), token, 12, TimeUnit.HOURS);
            redisUtil.setEx(RedisKeyConstant.TOKEN_ACCOUNT + token, user.getAccount(), 12, TimeUnit.HOURS);

            // 检查用户是否已经在线
            Boolean isOnline = redisUtil.sIsMember(RedisKeyConstant.ONLINE_USERS_SET_KEY, user.getAccount());
            if (isOnline == null || !isOnline) {
                // 如果用户不在线，发布 "Login:user:online" 消息
                log.info("发布 Login:user:online 消息");
                redisUtil.publishMessage(RedisKeyConstant.LOGIN_USER, user.getAccount());
            }

            //创建用户信息
            UserVo userVo = new UserVo();
            BeanUtil.copyProperties(user,userVo);
            return R.succeed(LoginUserVO.builder().userMsg(userVo).token(token).build());
        }
    }

    @Override
    public R<String> register(RegisterUserDTO registerUserDto) {
        //判断两次密码是否一致
        if(!registerUserDto.getPassword().equals(registerUserDto.getConfirmPassword())){
            throw new RoutineException(MessageConstant.TWO_IDENTICAL_PASSWORDS);
        }
        //判断邮箱是否为空
        if(registerUserDto.getEmail() == null){
            throw new RoutineException(MessageConstant.EMAIL_NOT);
        }
        // TODO 初步引入分布式锁，后续再优化
        //转换成User对象
        User user = new User();
        String accountToken = null;
        String account;
        //获取名为REGISTER_LOCK的Redisson分布式锁
        RLock lock = redissonClient.getLock(RedisKeyConstant.REGISTER_LOCK);
        boolean locked;
        //防止并发生成相同的账号，引入分布式锁
        try {
            // 尝试获取锁，锁的过期时间为10秒
            // TODO 10秒是测试情况具体的情况后续修改
            // 尝试获取锁，最多等待3秒，上锁后10秒自动解锁
            locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if(locked) {
                //生成账号
                account = generateAccount();
                //转换成User对象
                BeanUtil.copyProperties(registerUserDto,user);
                //添加账号
                user.setAccount(account);
                //密码加密
                user.setPassword(passwordEncoder.encode(registerUserDto.getPassword()));
                // 设置时间
                user.setCreationTime(LocalDateTime.now());
                user.setUpdateTime(LocalDateTime.now());
                //使用jwt加密账号
                accountToken = jwtTool.createToken(Long.valueOf(account), Duration.ofMinutes(10));
                //存入redis(十分钟过期)
                redisUtil.setEx(RedisKeyConstant.REGISTER_USER + account, user, 10, TimeUnit.MINUTES);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            throw new RoutineException(MessageConstant.REGISTRATION_ERROR);
        } finally {
            // 最后确保分布式锁的释放
            if (lock.isHeldByCurrentThread()) { // 检查当前线程是否保持该锁
                lock.unlock(); // 如果是，解锁
            }
        }

        //邮件短信
        //TODO 地址还是本机地址后期修改，该功能目前只能再本机测试
        emailUtils.sendEmail(user.getEmail(),"E-chat验证",
                MailConstant.MAIL_REGISTER_MSG_F +
                        user.getAccount() + "," +
                        MailConstant.MAIL_REGISTER_MSG_E +
                        "http://localhost:8099/register/validate_account?account="
                        + accountToken);
        return R.succeed();
    }

    @Override
    public R validateAccount(String account) {
        //获取用户id
        Long accountID = jwtTool.parseToken(account);
        //获取在redis中的用户信息，由于存入的是Object类型的数据，所以取出也是Object
        Object object = redisUtil.get(RedisKeyConstant.REGISTER_USER + accountID);
        if(object == null){
            throw new RoutineException(MessageConstant.REGISTRATION_TOKEN_EXPIRATION);
        } else {
            JacksonObjectMapper jacksonObjectMapper = new JacksonObjectMapper();
            //取出的对象是LinkedHashMap类型的
            //LinkedHashMap转成User
            User user = jacksonObjectMapper.convertValue(object, User.class);
            // 设置状态
            user.setState(1);
            log.info("user:{}",user);
            baseMapper.insert(user);
            redisUtil.delete(RedisKeyConstant.REGISTER_USER + accountID);
        }
        return R.succeed();
    }

    @Override
    public R loginOff(String token) {
//        String token = request.getHeader("token");
        Long userID = jwtTool.parseToken(token);
        Long userId = (Long) redisUtil.get(RedisKeyConstant.TOKEN_USER + token);
        //删除RedisKeyConstant.TOKEN_USER
        if (userId!=null && Objects.equals(userID, userId)){
            redisUtil.delete(RedisKeyConstant.TOKEN_USER + token);
        }
        String account = (String) redisUtil.get(RedisKeyConstant.TOKEN_ACCOUNT + token);
        if(account!=null && !account.isEmpty()){
            redisUtil.delete(RedisKeyConstant.TOKEN_ACCOUNT + token);
            redisUtil.delete(RedisKeyConstant.USER_ACCOUNT_ForToken + account);
        }
        // 检查用户是否已经在线
        Boolean isOnline = redisUtil.sIsMember(RedisKeyConstant.ONLINE_USERS_SET_KEY, account);
        if (isOnline != null && isOnline) {
            // 如果用户不在线，发布 "Login:user:online" 消息
            log.info("发布 Login:user:offline 消息");
            redisUtil.publishMessage(RedisKeyConstant.LOGIN_EXIT_USER, account);
        }
        return R.succeed();
    }

    @Override
    public String getMailOfAccount(String account) {
        User user = baseMapper
                .selectOne(
                        new LambdaQueryWrapper<User>()
                                .eq(User::getAccount, account)
                );
        if(user == null){
            throw new RoutineException(MessageConstant.ACCOUNT_NOT_FOUND);
        } else if(user.getEmail() == null || user.getEmail().isEmpty()){
            throw new RoutineException("当前账号邮箱不存在");
        } else {
            return user.getEmail();
        }
    }

    @Override
    public R resetPassword(ForgotPasswordDTO forgotPasswordDTO) {
        //获取凭证
        String voucherId = (String) redisUtil.get(RedisKeyConstant.VOUCHER + forgotPasswordDTO.getAccount());
        if (!forgotPasswordDTO.getPassword().equals(forgotPasswordDTO.getConfirmPassword())) {
            //密码不一致
            throw new RoutineException(MessageConstant.TWO_IDENTICAL_PASSWORDS);
        } else if (voucherId == null) {
            //没有凭证
            throw new RoutineException(MessageConstant.VOUCHER_ERROR);
        } else if (!forgotPasswordDTO.getVoucher().equals(voucherId)) {
            //凭证错误
            throw new RoutineException(MessageConstant.VOUCHER_ERROR);
        }
        else {
            String newPassword = passwordEncoder.encode(forgotPasswordDTO.getPassword());
            // 更新用户密码
            int updatedRows = baseMapper.update(null,
                    new LambdaUpdateWrapper<User>()
                            .eq(User::getAccount, forgotPasswordDTO.getAccount())
                            .set(User::getPassword, newPassword)
            );
            if (updatedRows == 0) {
                // 如果没有行被更新，那么账号可能不存在
                throw new RoutineException(MessageConstant.ACCOUNT_NOT_FOUND);
            }
        }
        return R.succeed();
    }

    @Override
    @Transactional
    public R userAppeal(UserAppealDTO userAppealDTO) {
        User user = baseMapper
                .selectOne(
                        new LambdaQueryWrapper<User>()
                                .eq(User::getAccount, userAppealDTO.getAccount())
                );
        if (user==null){
            //判断账号是否存在
            throw new RoutineException(MessageConstant.ACCOUNT_NOT_FOUND);
        } else {
            //获取凭证
            String voucherId = (String) redisUtil.get(RedisKeyConstant.VOUCHER + userAppealDTO.getAccount());
            if (!userAppealDTO.getVoucher().equals(voucherId)) {
                //凭证错误
                throw new RoutineException(MessageConstant.VOUCHER_ERROR);
            }
            UserAppeal userAppeal = new UserAppeal();
            userAppeal.setAccount(userAppealDTO.getAccount());
            userAppeal.setReason(userAppealDTO.getReason());
            userAppeal.setType(userAppealDTO.getType());
            userAppeal.setCreationTime(LocalDateTime.now());
            int insert = iUserAppealService.getBaseMapper().insert(userAppeal);
            if(insert == 0) {
                throw new RoutineException(MessageConstant.ADD_ERROR + "：申述失败");
            }
        }
        return R.succeed("申述成功");
    }

    @Override
    public R getAccountDisable(String account) {
        User user = baseMapper
                .selectOne(
                        new LambdaQueryWrapper<User>()
                                .eq(User::getAccount, account)
                );
        if (user==null){
            //判断账号是否存在
            throw new RoutineException(MessageConstant.ACCOUNT_NOT_FOUND);
        } else if(user.getState().equals(1)){
            //判断账号是否被锁定
            throw new RoutineException("账号未被锁定");
        }
        DisableAccount disableAccount = idisableAccountService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<DisableAccount>()
                        .eq(DisableAccount::getUserId, user.getId()));
        return R.succeed(disableAccount);
    }

    @Override
    public R getAccountUserFeedbackList(String account) {
        return R.succeed(iUserFeedbackService.getUserFeedbacksList(account));
    }

    @Override
    public R addOneUserFeedback(UserFeedbackDTO userFeedbackDTO) {
        User user = baseMapper
                .selectOne(
                        new LambdaQueryWrapper<User>()
                                .eq(User::getAccount, userFeedbackDTO.getAccount())
                );
        if (user==null){
            //判断账号是否存在
            throw new RoutineException(MessageConstant.ACCOUNT_NOT_FOUND);
        } else if(!user.getState().equals(1)){
            //判断账号是否被锁定
            throw new RoutineException("账号被锁定");
        }
        Integer integer = iUserFeedbackService.addUserFeedback(userFeedbackDTO);
        if(integer == 0){
            return R.failed("用户反馈" + MessageConstant.ADD_ERROR);
        } else {
            return R.succeed("用户反馈成功");
        }
    }

    @Override
    public R<VersionNumberVO> getNewVersion() {
        Version newVersion = iVersionService.getNewVersion();
        VersionNumberVO versionNumberVO = new VersionNumberVO();
        versionNumberVO.setVersionNumber(newVersion.getVersionNumber());
        return R.succeed(versionNumberVO);
    }


    /**
     * 生成账号
     * 未测试
     *
     * @return
     */
    public String generateAccount() {
        String account;
        User user = null;
        Boolean accountInRedisExists;
        do {
            // 首先随机生成账号的长度，长度范围在6-10位
            int length = RandomUtil.randomInt(6, 11);
            // 确保首位数字为1-9之间
            int firstDigit = RandomUtil.randomInt(1, 10);
            // 其他位置的数字可以为0-9之间
            String otherDigits = RandomUtil.randomNumbers(length - 1);
            // 拼接首位数字和其他数字
            account = firstDigit + otherDigits;
            // 从Redis查询账户
            accountInRedisExists = redisUtil.hasKey(RedisKeyConstant.REGISTER_USER + account);
            if (!accountInRedisExists){
                //不存在再去数据库中查
                user = baseMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getAccount, account));
            }
        } while (accountInRedisExists || user != null); // 如果用户存在，则重新生成账号

        return account;
    }
}
